package com.shiwaixiangcun.authz.shiro.realm;

import com.shiwaixiangcun.authz.shiro.oauth.OAuth2Token;
import com.shiwaixiangcun.core.domain.account.Account;
import com.shiwaixiangcun.core.domain.oauth.AccessToken;
import com.shiwaixiangcun.core.domain.oauth.ClientDetails;
import com.shiwaixiangcun.core.service.oauth.OAuthRSService;
import com.shiwaixiangcun.core.shiro.exception.OAuth2AuthenticationException;
import com.shiwaixiangcun.core.shiro.exception.OAuth2TokenAuthenticationException;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import java.util.Set;

public class OAuth2Realm extends AuthorizingRealm {

    private static final Logger LOG = LoggerFactory.getLogger(OAuth2Realm.class);

    @Autowired
    private OAuthRSService rsService;


    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof OAuth2Token;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Account account = (Account) principals.getPrimaryPrincipal();

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();


        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        OAuth2Token upToken = (OAuth2Token) token;
        final String accessToken = (String) upToken.getCredentials();

        if (StringUtils.isEmpty(accessToken)) {
            throw new OAuth2AuthenticationException("Invalid access_token: " + accessToken);
        }
        //Validate access token
        AccessToken aToken = rsService.loadAccessTokenByTokenId(accessToken);
        validateToken(accessToken, aToken);

        //Validate client details by resource-id
        final ClientDetails clientDetails = rsService.loadClientDetails(aToken.getClientId());
        validateClientDetails(accessToken, aToken, clientDetails);

        Account account = rsService.loadAccount(aToken.getAccount());
        // Null account is invalid
        if (account == null) {
            throw new AccountException("Null usernames are not allowed by this realm.");
        }

        return new SimpleAuthenticationInfo(account, accessToken, getName());
    }

    private void validateToken(String token, AccessToken accessToken) throws OAuth2AuthenticationException {
        if (accessToken == null) {
            LOG.debug("Invalid access_token: {}, because it is null", token);
            throw new OAuth2TokenAuthenticationException("Invalid access_token: " + token);
        }
        if (accessToken.tokenExpired()) {
            LOG.debug("Invalid access_token: {}, because it is expired", token);
            throw new OAuth2TokenAuthenticationException("Invalid access_token: " + token);
        }
    }

    private void validateClientDetails(String token, AccessToken accessToken, ClientDetails clientDetails) throws OAuth2AuthenticationException {
        if (clientDetails == null || clientDetails.getDeleted()) {
            LOG.debug("Invalid ClientDetails: {} by client_id: {}, it is null or archived", clientDetails, accessToken.getClientId());
            throw new OAuth2AuthenticationException("Invalid client by token: " + token);
        }
    }


}
